//------------------------------------------------------------------------------
//  <copyright file="RollingAverage.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//  </copyright>
//------------------------------------------------------------------------------

namespace Microsoft.Robotics.Numerics
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    /// <summary>
    /// Calculates a rolling average.
    /// </summary>
    /// <typeparam name="T">A numeric type.  Will throw exception when adding values if the value cannot be converted to a double.</typeparam>
    public class RollingAverage<T> where T : IComparable, IComparable<T>, IEquatable<T>
    {
        /// <summary>
        /// Array of samples
        /// </summary>
        private T[] samples;

        /// <summary>
        /// Current index for new samples
        /// </summary>
        private int index;

        /// <summary>
        /// number of samples
        /// </summary>
        private int sampleSize;

        /// <summary>
        /// have the samples been filled yet?
        /// </summary>
        private bool samplesFull;

        /// <summary>
        /// Initializes a new instance of the <see cref="RollingAverage{T}" /> class.
        /// </summary>
        /// <param name="sampleSize">Number of samples</param>
        public RollingAverage(int sampleSize)
        {
            this.Init(sampleSize);
        }
        
        /// <summary>
        /// Gets a value indicating whether all the sample slots been filled at least once
        /// </summary>
        public int SampleCount 
        {
            get { return this.samplesFull ? this.sampleSize : this.index; }
        }

        /// <summary>
        /// Gets the average of all the samples
        /// </summary>
        public double Average 
        { 
            get 
            {
                if (this.SampleCount == 0)
                {
                    return 0;
                }
                else
                {
                    return this.samples.Take(this.SampleCount).Average(x => System.Convert.ToDouble(x));
                }
            }
        }

        /// <summary>
        /// Add a sample to the sample set
        /// </summary>
        /// <param name="sample">Sample value</param>
        public void AddSample(T sample)
        {
            // will throw an exception if conversion fails.  
            // Since only numbers can be averaged, if this fails then the averaging will fail.
            System.Convert.ToDouble(sample);

            if (this.samples != null)
            {
                this.samples[this.index] = sample;
                ++this.index;
                if (this.index >= this.sampleSize)
                {
                    this.samplesFull = true;
                    this.index = 0;
                }
            }
        }

        /// <summary>
        /// Initialize the class with a particular sample size
        /// </summary>
        /// <param name="sampleSize">The number of samples</param>
        private void Init(int sampleSize)
        {
            this.sampleSize = sampleSize;
            this.samples = new T[sampleSize];
        }
    }
}
